#include "Screen.h"

#include "Tools.h"

struct _Screen
{
        Spi parent_instance;
        // LCD 参数
        uint16_t width;
        uint16_t height;
        uint16_t setxcmd;
        uint16_t setycmd;
        uint16_t wramcmd;
};

// GPIO 置位
#define LCD_CS_SET GPIOA->BSRR = 1 << 11
#define LCD_RS_SET GPIOA->BSRR = 1 << 6

// GPIO 复位
#define LCD_CS_CLR GPIOA->BRR = 1 << 11
#define LCD_RS_CLR GPIOA->BRR = 1 << 6

// LCD 宽度
#define LCD_W 240
// LCD 高度
#define LCD_H 320

static uint8_t is_inited = false;
static Screen  global_screen;
uint16_t       screen_background_color = 0xffff;

/**
 * @brief 厂家给出的写指令函数
 */
static void
lcd_wr_reg(Screen *self, const uint8_t data)
{
        LCD_CS_CLR;
        LCD_RS_CLR;
        spi_send_and_receive(SPI(self), data);
        LCD_CS_SET;
}

/**
 * @brief 厂家给出的写数据函数
 */
static void
lcd_wr_data(Screen *self, uint8_t data)
{
        LCD_CS_CLR;
        LCD_RS_SET;
        spi_send_and_receive(SPI(self), data);
        LCD_CS_SET;
}

/**
 * @brief 写数据到寄存器
 */
static void
lcd_write_reg(Screen *self, uint8_t lcd_reg, const uint8_t lcd_reg_value)
{
        lcd_wr_reg(self, lcd_reg);
        lcd_wr_data(self, lcd_reg_value);
}

/**
 * @brief 写 16 位数据到 lcd 屏幕
 */
static void
lcd_write_data_16bit(Screen *self, const uint16_t data)
{
        LCD_CS_CLR;
        LCD_RS_SET;
        spi_send_and_receive(SPI(self), data >> 8);
        spi_send_and_receive(SPI(self), data);
        LCD_CS_SET;
}

/**
 * @brief 设置LCD显示窗口
 * @param xstart LCD显示窗口的x坐标初始位置
 * @param ystart LCD显示窗口的y坐标初始位置
 * @param xend LCD显示窗口的x坐标结束
 * @param yend LCD显示窗口的y坐标结束
 */
static void
lcd_set_windows(Screen        *self,
                const uint16_t xstart,
                const uint16_t ystart,
                const uint16_t xend,
                const uint16_t yend)
{
        lcd_wr_reg(self, self->setxcmd);
        lcd_wr_data(self, xstart >> 8);
        lcd_wr_data(self, 0x00ff & xstart);
        lcd_wr_data(self, xend >> 8);
        lcd_wr_data(self, 0x00ff & xend);

        lcd_wr_reg(self, self->setycmd);
        lcd_wr_data(self, ystart >> 8);
        lcd_wr_data(self, 0x00ff & ystart);
        lcd_wr_data(self, yend >> 8);
        lcd_wr_data(self, 0x00ff & yend);

        /* 开始写入 GRAM */
        lcd_wr_reg(self, self->wramcmd);
}

/**
 * @brief 设置 LCD 方向
 */
static void
screen_set_orientation(Screen *self, ScreenOrientation orien)
{
        self->setxcmd = 0x2a;
        self->setycmd = 0x2b;
        self->wramcmd = 0x2c;

        self->width  = LCD_W;
        self->height = LCD_H;
        lcd_write_reg(self, 0x36, 1 << 3 | 1 << 6 | 1 << 7);
        /*
        switch (orien) {
        case SCREEN_ORIENTATION_HORIZONTAL:
                self->width  = LCD_W;
                self->height = LCD_H;
                lcd_write_reg(self, 0x36, (1 << 3) | (1 << 6) | (1 << 7));
                break;
        case SCREEN_ORIENTATION_VERTICAL:
                self->width  = LCD_H;
                self->height = LCD_W;
                lcd_write_reg(self,
                              0x36,
                              (1 << 3) | (0 << 7) | (1 << 6) | (1 << 5));
                break;
        default:
                self->width  = LCD_W;
                self->height = LCD_H;
                lcd_write_reg(self, 0x36, (1 << 3) | (0 << 6) | (0 << 7));
                break;
        }*/
}

static void
lcd_init(Screen *self)
{
        GPIO_InitTypeDef gpio;
        RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
        gpio.GPIO_Pin   = GPIO_Pin_6 | GPIO_Pin_11;
        gpio.GPIO_Speed = GPIO_Speed_50MHz;
        gpio.GPIO_Mode  = GPIO_Mode_Out_PP;
        GPIO_Init(GPIOA, &gpio);

        //*************2.8inch ILI9341初始化**********//
        lcd_wr_reg(self, 0xCF);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0xC9); // C1
        lcd_wr_data(self, 0X30);
        lcd_wr_reg(self, 0xED);
        lcd_wr_data(self, 0x64);
        lcd_wr_data(self, 0x03);
        lcd_wr_data(self, 0X12);
        lcd_wr_data(self, 0X81);
        lcd_wr_reg(self, 0xE8);
        lcd_wr_data(self, 0x85);
        lcd_wr_data(self, 0x10);
        lcd_wr_data(self, 0x7A);
        lcd_wr_reg(self, 0xCB);
        lcd_wr_data(self, 0x39);
        lcd_wr_data(self, 0x2C);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x34);
        lcd_wr_data(self, 0x02);
        lcd_wr_reg(self, 0xF7);
        lcd_wr_data(self, 0x20);
        lcd_wr_reg(self, 0xEA);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_reg(self, 0xC0);  // Power control
        lcd_wr_data(self, 0x1B); // VRH[5:0]
        lcd_wr_reg(self, 0xC1);  // Power control
        lcd_wr_data(self, 0x00); // SAP[2:0];BT[3:0] 01
        lcd_wr_reg(self, 0xC5);  // VCM control
        lcd_wr_data(self, 0x30); // 3F
        lcd_wr_data(self, 0x30); // 3C
        lcd_wr_reg(self, 0xC7);  // VCM control2
        lcd_wr_data(self, 0XB7);
        lcd_wr_reg(self, 0x36); // Memory Access Control
        lcd_wr_data(self, 0x08);
        lcd_wr_reg(self, 0x3A);
        lcd_wr_data(self, 0x55);
        lcd_wr_reg(self, 0xB1);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x1A);
        lcd_wr_reg(self, 0xB6); // Display Function Control
        lcd_wr_data(self, 0x0A);
        lcd_wr_data(self, 0xA2);
        lcd_wr_reg(self, 0xF2); // 3Gamma Function Disable
        lcd_wr_data(self, 0x00);
        lcd_wr_reg(self, 0x26); // Gamma curve selected
        lcd_wr_data(self, 0x01);
        lcd_wr_reg(self, 0xE0); // Set Gamma
        lcd_wr_data(self, 0x0F);
        lcd_wr_data(self, 0x2A);
        lcd_wr_data(self, 0x28);
        lcd_wr_data(self, 0x08);
        lcd_wr_data(self, 0x0E);
        lcd_wr_data(self, 0x08);
        lcd_wr_data(self, 0x54);
        lcd_wr_data(self, 0XA9);
        lcd_wr_data(self, 0x43);
        lcd_wr_data(self, 0x0A);
        lcd_wr_data(self, 0x0F);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_reg(self, 0XE1); // Set Gamma
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x15);
        lcd_wr_data(self, 0x17);
        lcd_wr_data(self, 0x07);
        lcd_wr_data(self, 0x11);
        lcd_wr_data(self, 0x06);
        lcd_wr_data(self, 0x2B);
        lcd_wr_data(self, 0x56);
        lcd_wr_data(self, 0x3C);
        lcd_wr_data(self, 0x05);
        lcd_wr_data(self, 0x10);
        lcd_wr_data(self, 0x0F);
        lcd_wr_data(self, 0x3F);
        lcd_wr_data(self, 0x3F);
        lcd_wr_data(self, 0x0F);
        lcd_wr_reg(self, 0x2B);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x01);
        lcd_wr_data(self, 0x3f);
        lcd_wr_reg(self, 0x2A);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0x00);
        lcd_wr_data(self, 0xef);
        lcd_wr_reg(self, 0x11); // Exit Sleep
        delay_ms(120);
        lcd_wr_reg(self, 0x29); // display on

        screen_set_orientation(self, SCREEN_ORIENTATION);
}

Screen *
screen_new_from_global(void)
{
        if (is_inited)
                return &global_screen;

        spi_init(SPI(&global_screen),
                 SPI_TYPE_SPI1,
                 SPI_CHOOSE_SCK | SPI_CHOOSE_MOSI);
        lcd_init(&global_screen);
        is_inited = true;
        return &global_screen;
}

void
screen_set_draw_area(Screen        *self,
                     const uint16_t startX,
                     const uint16_t startY,
                     const uint16_t endX,
                     const uint16_t endY)
{
        lcd_set_windows(self, startX, startY, endX, endY);
}

void
screen_draw_pixel(Screen *self, const uint16_t pixel_color)
{
        lcd_write_data_16bit(self, pixel_color);
        /* 经测试函数调用进栈几乎不会影响屏幕刷新速度
        LCD_CS_CLR;
        LCD_RS_SET;
        while ((SPI1->SR & SPI_I2S_FLAG_TXE) == RESET)
                ;
        SPI1->DR = pixel_color >> 8;
        while ((SPI1->SR & SPI_I2S_FLAG_RXNE) == RESET)
                ;
        __temp_var = SPI1->DR;
        while ((SPI1->SR & SPI_I2S_FLAG_TXE) == RESET)
                ;
        SPI1->DR = pixel_color & 0x00ff;
        while ((SPI1->SR & SPI_I2S_FLAG_RXNE) == RESET)
                ;
        __temp_var = SPI1->DR;
        LCD_CS_SET;*/
}
